package la.iok.hzsvn.lewin.movie.cloud.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import la.iok.hzsvn.lewin.movie.cloud.config.RedisKey;
import la.iok.hzsvn.lewin.movie.cloud.entity.OrgUnit;
import la.iok.hzsvn.lewin.movie.cloud.entity.OrgUnitAddress;
import la.iok.hzsvn.lewin.movie.cloud.entity.QRCode;
import la.iok.hzsvn.lewin.movie.cloud.entity.Room;
import la.iok.hzsvn.lewin.movie.cloud.mapper.QRCodeMapper;
import la.iok.hzsvn.lewin.movie.cloud.model.PlayRecordQrCodeInfo;
import la.iok.hzsvn.lewin.movie.cloud.model.QRCodeInfo;
import la.iok.hzsvn.lewin.movie.core.RoleEnum;
import la.iok.hzsvn.lewin.movie.core.Token;
import la.iok.hzsvn.lewin.movie.core.exception.ErrorCode;
import la.iok.hzsvn.lewin.movie.core.model.PlayRecordVo;
import la.iok.hzsvn.lewin.movie.core.model.QR;
import la.iok.hzsvn.lewin.movie.core.model.QRCodeBaseInfo;
import la.iok.hzsvn.lewin.movie.services.MovieServiceInvoker;
import la.iok.hzsvn.lewin.mybatis.service.BaseServiceImpl;
import la.iok.hzsvn.redis.service.RedisService;
import la.iok.hzsvn.share.annotations.NotNull;
import la.iok.hzsvn.share.annotations.Nullable;
import la.iok.hzsvn.share.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Service
public class QRCodeServiceImpl extends BaseServiceImpl<QRCode,QRCodeMapper,Long> implements QRCodeService{
    private static final Logger logger = LoggerFactory.getLogger(QRCodeService.class);
    private final OrgUnitService orgUnitService;
    private final OrgUnitAddressService orgUnitAddressService;
    private final RedisService redisService;
    private final RedisKey redisKey;
    private final ExecutorService executorService;
    private final ObjectMapper objectMapper;
    private final RoomService roomService;
    private final MovieServiceInvoker movieServiceInvoker;

    public QRCodeServiceImpl(OrgUnitService orgUnitService,
                             OrgUnitAddressService orgUnitAddressService,
                             RedisService redisService,
                             RedisKey redisKey,
                             QRCodeMapper qrCodeMapper,
                             ObjectMapper objectMapper,
                             RoomService roomService,
                             MovieServiceInvoker movieServiceInvoker) {
        super(qrCodeMapper);
        this.orgUnitService = orgUnitService;
        this.orgUnitAddressService = orgUnitAddressService;
        this.redisService = redisService;
        this.redisKey = redisKey;
        this.objectMapper = objectMapper;
        this.roomService = roomService;
        this.movieServiceInvoker = movieServiceInvoker;
        this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    @NotNull
    @Override
    public QRCodeInfo shopQRCode(@NotNull Token token, @NotNull Long id) {
        OrgUnit orgUnit = orgUnitService.select(id);
        ErrorCode.ORG_UNIT_NOT_EXIST.assertNotNull(orgUnit,id);
        validateOrgUnitAuthority(token, orgUnit);
        OrgUnitAddress address = orgUnitAddressService.orgUnitAddress(id);
        QRCodeInfo qrCode = QRCodeBaseInfo.generateQrCodeInfo(QRCodeInfo.class,qrExpiredTime());
        qrCodeInfo(qrCode,orgUnit.getName(), address);
        redisService.hash(redisKey.qrKey(qrCode.getUuid()),new QR(QR.TYPE_ORG_UNIT,id));
        redisService.expire(redisKey.qrKey(qrCode.getUuid()),2, TimeUnit.HOURS);
        executorService.submit(()->save(id,qrCode));
        return qrCode;
    }

    private void validateOrgUnitAuthority(Token token, OrgUnit orgUnit) {
        if(token.hasAuthority(RoleEnum.Admin)){
            //都可以
        }else if(token.hasAuthority(RoleEnum.Manager)){
            Set<Long> set = new HashSet<>(orgUnitService.childOrgUnitIds(token.getOrgUnitId()));
            set.add(token.getOrgUnitId());
            ErrorCode.ORG_UNIT_ACCESS_DENIED.assertTrue(set.contains(orgUnit.getId()), orgUnit.getName());
        }else if(token.hasAuthority(RoleEnum.ShopManager)){
            ErrorCode.ORG_UNIT_ACCESS_DENIED.assertEqual(token.getOrgUnitId(), orgUnit.getId(), orgUnit.getName());
        }else {
            ErrorCode.ORG_UNIT_ACCESS_DENIED.fail(orgUnit.getName());
        }
    }

    @NotNull
    @Override
    public QRCodeInfo roomQRCode(@NotNull Token token, @NotNull Long id) {
        Room room = roomService.select(id);
        ErrorCode.ROOM_NOT_EXIST.assertNotNull(room,id);
        OrgUnit orgUnit = orgUnitService.select(room.getOrgUnitId());
        ErrorCode.ORG_UNIT_NOT_EXIST.assertNotNull(orgUnit,room.getOrgUnitId());
        validateOrgUnitAuthority(token,orgUnit);

        OrgUnitAddress address = orgUnitAddressService.orgUnitAddress(room.getOrgUnitId());
        QRCodeInfo qrCode = QRCodeBaseInfo.generateQrCodeInfo(QRCodeInfo.class, qrExpiredTime());
        qrCodeInfo(qrCode,orgUnit.getName() + "-" + room.getName(), address);
        redisService.hash(redisKey.qrKey(qrCode.getUuid()),new QR(QR.TYPE_ROOM,id));
        redisService.expire(redisKey.qrKey(qrCode.getUuid()),2, TimeUnit.HOURS);
        executorService.submit(()->save(orgUnit.getId(),id,qrCode));
        return qrCode;
    }

    private Date qrExpiredTime(){
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR,2);
        return calendar.getTime();
    }

    private void qrCodeInfo(@NotNull QRCodeInfo qrCode, @NotNull String name, OrgUnitAddress address){
        qrCode.setName(name);
        if(address != null){
            qrCode.setAddr(address.getAddr());
            qrCode.setDetailAddress(address.getDetailAddress());
        }
    }

    @NotNull
    @Override
    public QR qrCode(@NotNull String uuid) {
        String key = redisKey.qrKey(uuid);
        QR qr = redisService.hash(key,QR.class);
        ErrorCode.QR_CODE_EXPIRED.assertNotNull(qr,key);
        return qr;
    }

    @Override
    public void save(@NotNull Long orgUnitId,@NotNull QRCodeInfo qrCode) {
        save(orgUnitId,null,qrCode);
    }

    @Override
    public void save(@NotNull Long orgUnitId,@Nullable Long roomId,@NotNull QRCodeBaseInfo qrCode) {
        QRCode qr = new QRCode();
        try {
            qr.setContent(objectMapper.writeValueAsString(qrCode));
        } catch (JsonProcessingException e) {
            logger.error("json序列化二维码内容失败,内容将保存为空",e);
        }
        qr.setCreateTime(new Date());
        qr.setExpiredTime(qrCode.getExpiredTime());
        qr.setOrgUnitId(orgUnitId);
        qr.setRoomId(roomId);
        qr.setUuid(qrCode.getUuid());
        mapper.insertSelective(qr);
    }

    @Nullable
    @Override
    public QRCode select(@Nullable String uuid) {
        if(StringUtils.isBlank(uuid))
            return null;
        return mapper.selectByProperty("uuid",uuid);
    }

    @Override
    public void delete(String uuid) {
        if(StringUtils.isBlank(uuid))
            return;
        mapper.deleteByProperty("uuid",uuid);
    }

    @Override
    public PlayRecordQrCodeInfo playRecordQRCode(@NotNull Token token, @Nullable Long id) {
        PlayRecordVo playRecord = movieServiceInvoker.playRecord(id);
        ErrorCode.RECORD_NOT_EXIST.assertNotNull(playRecord,id);
        ErrorCode.PLAY_RECORD_ACCESS_DENIED.assertEqual(token.getUserId(),playRecord.getDmId(),id);
        ErrorCode.PLAY_RECORD_ENDED.assertTrue(playRecord.playing(),id);

        PlayRecordQrCodeInfo qrCode = QRCodeBaseInfo.generateQrCodeInfo(PlayRecordQrCodeInfo.class,qrExpiredTime());
        qrCode.setMovieName(playRecord.getMovieName());
        qrCode.setMovieVersion(playRecord.getMovieVersion());
        qrCode.setMovieId(playRecord.getMovieId());
        redisService.hash(redisKey.qrKey(qrCode.getUuid()),new QR(QR.TYPE_PLAY_RECORD,id));
        redisService.expire(redisKey.qrKey(qrCode.getUuid()),2, TimeUnit.HOURS);
        executorService.submit(()->save(playRecord.getOrgUnitId(),playRecord.getRoomId(),qrCode));
        return qrCode;
    }
}
